package cn.funnymap.lgis.file;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 文件工具类
 *
 * @author jiaoxn
 */
public class FileUtil {
    private FileUtil() {}

    /**
     * 获取指定文件夹的大小
     *
     * @param directory 指定的文件夹
     * @return 文件夹大小，单位：字节
     */
    public static long getDirectorySize(Path directory) throws IOException {
        long size = 0;

        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
            for (Path file: directoryStream) {
                if (Files.isDirectory(file)) {
                    size += getDirectorySize(file);
                } else {
                    size += Files.size(file);
                }
            }
        }
        return size;
    }

    /**
     * 将指定的字节大小转为可读性更好的字符串
     *
     * @param size 文件大小，单位：字节
     * @return 可读性更好的字符串，携带单位，例如：2.3 GB
     */
    public static String readableFileSize(long size) {
        if (size <= 0) {
            return "0";
        }

        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        return new DecimalFormat("#,###.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定文件扩展的文件路径
     *
     * @param targetDir 目标文件夹
     * @param extensions 指定的文件扩展
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFileExtension(Path targetDir, String... extensions) throws IOException {
        List<Path> response = new ArrayList<>();

        try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir, entry -> {
            if (Files.isDirectory(entry)) {
                response.addAll(getFileByFileExtension(entry, extensions));
            } else {
                String fileName = entry.getFileName().toString();
                for (String extension : extensions) {
                    if (fileName.endsWith(extension)) {
                        return true;
                    }
                }
            }

            return false;
        })) {
            for (Path file: stream) {
                response.add(file);
            }
        }

        return response;
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定名称的文件路径
     *
     * @param targetDir 目标文件夹
     * @param fileName 指定的文件名称
     * @param isNested 是否嵌套子级文件夹查询
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFileName(Path targetDir, String fileName, boolean isNested) throws IOException {
        List<Path> foundFiles = new ArrayList<>();

        if (!Files.isDirectory(targetDir)) {
            return foundFiles;
        }

        if (isNested) {
            // 嵌套查询，walkFileTree 的效率比 DirectoryStream 更高一些
            Files.walkFileTree(targetDir, new SimpleFileVisitor<Path>() {
                @NotNull
                @Override
                public FileVisitResult visitFile(Path file, @Nullable BasicFileAttributes attrs) {
                    if (isTheSameFilename(file, fileName)) {
                        foundFiles.add(file);
                    }

                    return FileVisitResult.CONTINUE;
                }
            });
        } else {
            try(DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir)) {
                for (Path file : stream) {
                    if (Files.isRegularFile(file) && isTheSameFilename(file, fileName)) {
                        foundFiles.add(file);
                    }
                }
            }
        }

        return foundFiles;
    }

    /**
     * 获取指定文件的文件扩展名
     *
     * @param path 目标文件
     * @return 文件的扩展名，例如：.zip、.txt
     */
    public static String getFileExtension(Path path) {
        // 如果是文件夹，返回null
        if (Files.isDirectory(path)) {
            return null;
        }

        String fileName = path.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex);
        }

        return null;
    }

    /**
     * 从文件名称中获取文件扩展名
     *
     * @param fileName 文件名称
     * @return 文件扩展名
     */
    public static String getFileExtension(String fileName) {
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1 && dotIndex < fileName.length() - 1) {
            return fileName.substring(dotIndex);
        }

        return null;
    }

    /**
     * 获取不带有文件扩展的文件名
     *
     * @param path 目标文件
     * @return 不带文件扩展的文件名，例如：对于“测试.zip”返回“测试”
     */
    public static String getFileNameWithoutExtension(Path path) {
        if (Files.isDirectory(path)) {
            return null;
        }

        String fileName = path.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex != -1) {
            return fileName.substring(0, dotIndex);
        }
        return fileName;
    }

    /**
     * 将ZIP文件解压到临时目录
     *
     * @param zipFilePath 需要解压的ZIP文件
     * @return 解压后的临时目录，如果输入的数据不是ZIP文件夹，则返回null
     */
    public static Path extractZipFileToTemp(Path zipFilePath) throws IOException {
        // 如果不是ZIP文件，则直接返回null
        if (validateFileType(zipFilePath, FileType.ZIP)) {
            String fileName = getFileNameWithoutExtension(zipFilePath);
            Path tempDir = Files.createTempDirectory(fileName);

            // 解压ZIP文件到临时目录
            extractZipFile(zipFilePath, tempDir);

            return tempDir;
        }

        return null;
    }

    public static void extractZipFile(Path zipFilePath, Path targetDir) throws IOException {
        // 如果不是ZIP文件，则直接返回null
        if (!validateFileType(zipFilePath, FileType.ZIP)) {
            return;
        }

        try (ZipInputStream zipInputStream = new ZipInputStream(Files.newInputStream(zipFilePath))) {
            ZipEntry zipEntry = zipInputStream.getNextEntry();

            while (zipEntry != null) {
                Path filePath = Paths.get(targetDir.toAbsolutePath().toString(), zipEntry.getName());
                if (zipEntry.isDirectory()) {
                    Files.createDirectories(filePath);
                } else {
                    Files.createDirectories(filePath.getParent());
                    Files.copy(zipInputStream, filePath);
                }

                zipInputStream.closeEntry();
                zipEntry = zipInputStream.getNextEntry();
            }
        }
    }

    public static Path saveMultipartFileToTemp(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return null;
        }

        String originalFilename = multipartFile.getOriginalFilename();
        if (originalFilename == null) {
            return null;
        }

        String fileSuffix = getFileExtension(originalFilename);
        if (fileSuffix == null) {
            return null;
        }

        String newFileName = UUID.randomUUID().toString();
        Path newFile = Files.createTempFile(newFileName, fileSuffix);

        try {
            multipartFile.transferTo(newFile);
            return newFile;
        } catch (IOException ioException) {
            throw new RuntimeException("内存中创建ZIP文件出错");
        }
    }

    public static boolean validateFileType(String fileName, FileType targetFileType) {
        String fileExtension = getFileExtension(fileName);
        return targetFileType.getValue().equalsIgnoreCase(fileExtension);
    }
    public static boolean validateFileType(Path path, FileType targetFileType) {
        String fileExtension = getFileExtension(path);
        return targetFileType.getValue().equalsIgnoreCase(fileExtension);
    }

    public static void delete(Path path) throws IOException {
        if (Files.isDirectory(path)) {
            Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                @NotNull
                @Override
                public FileVisitResult visitFile(Path file, @Nullable BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @NotNull
                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }

                @NotNull
                @Override
                public FileVisitResult visitFileFailed(Path file, @NotNull IOException exc) throws IOException {
                    throw exc;
                }
            });
        } else {
            Files.delete(path);
        }
    }

    /**
     * 在指定文件夹中（包含其子级文件夹）获取指定名称的文件路径
     *
     * @param targetDir 目标文件夹
     * @param fileName 指定的文件名称
     * @param isNested 是否嵌套子级文件夹查询
     * @return 满足检索结果的文件的文件路径
     */
    public static List<Path> getFileByFuzzyMatchingFilename(Path targetDir, String fileName, boolean isNested) throws IOException {
        List<Path> foundFiles = new ArrayList<>();

        if (!Files.isDirectory(targetDir)) {
            return foundFiles;
        }

        if (isNested) {
            // 嵌套查询，walkFileTree 的效率比 DirectoryStream 更高一些
            Files.walkFileTree(targetDir, new SimpleFileVisitor<Path>() {
                @NotNull
                @Override
                public FileVisitResult visitFile(Path file, @Nullable BasicFileAttributes attrs) {
                    if (isFilenameFuzzySame(file, fileName)) {
                        foundFiles.add(file);
                    }

                    return FileVisitResult.CONTINUE;
                }
            });
        } else {
            try(DirectoryStream<Path> stream = Files.newDirectoryStream(targetDir)) {
                for (Path file : stream) {
                    if (Files.isRegularFile(file) && isFilenameFuzzySame(file, fileName)) {
                        foundFiles.add(file);
                    }
                }
            }
        }

        return foundFiles;
    }

    private static boolean isTheSameFilename(@NotNull Path path, @NotNull String targetName) {
        return Objects.equals(path.getFileName().toString(), targetName);
    }

    private static boolean isFilenameFuzzySame(@NotNull Path path, @NotNull String targetName) {
        return path.getFileName().toString().contains(targetName);
    }
}
